#include <mpi.h>
#include <ctime>
#include <cstdlib>
#include "Servidor.h"
#include "MPIUtil.h"

Servidor::Servidor() {
    MPI_Comm_size(MPI_COMM_WORLD, &quantidadeNos);
    MPI_Comm_rank(MPI_COMM_WORLD, &id);
    MPI_Get_processor_name(nome, &tamanhoNome);
}

Servidor::~Servidor() {
    printf("processo %d: terminei\n", id);
}

void Servidor::init() {
    if (isCoordenador()) {
        printf("processo %d: iniciei\n", id);
        if (canCommit()) {
            doCommit();
            MPIUtil mpiUtil;
            int origem;
            int mensagem;

            for (int i = 0; i < quantidadeNos; i++) {
                if (id != i) {
                    mensagem = mpiUtil.receber(&origem);
                    printf("processo %d: recebi uma mensagem HAVE_COMMITTED do processo %d\n",
                           id, origem);
                }
            }
        } else {
            doAbort();
        }
    } else {
        printf("processo %d: iniciei\n", id);
        MPIUtil mpiUtil;

        int origem;
        int mensagem = mpiUtil.receber(&origem);
        if (mensagem == CAN_COMMIT) {
            printf("processo %d: recebi uma mensagem CAN_COMMIT do processo %d\n",
                    id, origem);
            decisao = responder();

            mpiUtil.enviar(decisao, origem);
            printf("processo %d: estou enviado uma resposta (%s)\n",
                    id, (decisao == YES ? "YES" : "NO"));
        }

        printf("processo %d: estou esperando uma resposta\n", id);
        mensagem = mpiUtil.receber(&origem);
        if (mensagem == DO_COMMIT) {
            printf("processo %d: comitei\n", id);
            haveCommitted();
        } else if (mensagem == DO_ABORT) {
            printf("processo %d: abortei\n", id);
        }
    }
}

int Servidor::responder() {
    printf("processo %d: respondi %s\n", id, (decisao == YES ? "YES" : "NO"));
    srand(time(NULL) + id);
    return ((float) rand() / RAND_MAX) < PROBABILIDADE ? NO : YES;
}

bool Servidor::ocorreuErro() {
    printf("processo %d: ocorreu um erro\n", id);
    srand(time(NULL) + id);
    return ((float) rand() / RAND_MAX) < PROBABILIDADE ? true : false;
}

void Servidor::simularProcessamento() {
    printf("processo %d: realizando algum processamento\n", id);
    srand(time(NULL) + id);
    sleep(rand() % TEMPO_PROCESSAMENTO_MAXIMO + 1);
}

bool Servidor::canCommit() {
    bool valorFinalRespostas = true;
    MPI_Status estados[quantidadeNos];
    MPIUtil mpiUtil;
    int origem;
    for (int i = 0; i < quantidadeNos; i++) {
        if (id != i) {
            printf("processo %d: enviei uma mensagem CAN_COMMIT para o processo %d\n",
                    id, i);
            mpiUtil.enviar(CAN_COMMIT, i);
        }
    }

    for (int i = 0; i < quantidadeNos; i++) {
        if (id == i) {
            decisao = responder();
        } else {
            printf("processo %d: estou esperando uma resposta\n", id);
            decisao = mpiUtil.receber(&origem);
            printf("processo %d: recebi uma mensagem do processo %d com %s\n",
                    id, i, (decisao == YES ? "YES" : "NO"));
        }

        if (decisao == NO) {
            valorFinalRespostas = false;
        }
    }

    if (valorFinalRespostas) {
        return true;
    }

    return false;
}

void Servidor::doCommit() {
    MPIUtil mpiUtil;

    printf("processo %d: comitei\n", id);

    for (int i = 0; i < quantidadeNos; i++) {
        if (id != i) {
            printf("processo %d: enviei um DO_COMMIT para o processo %d\n", id, i);
            mpiUtil.enviar(DO_COMMIT, i);
        }
    }
}

void Servidor::doAbort() {
    MPIUtil mpiUtil;

    printf("processo %d: abortei\n", id);

    for (int i = 0; i < quantidadeNos; i++) {
        if (id != i) {
            printf("processo %d: enviei um DO_ABORT para o processo %d\n", id, i);
            mpiUtil.enviar(DO_ABORT, i);
        }
    }
}

void Servidor::haveCommitted() {
    MPIUtil mpi;
    mpi.enviar(HAVE_COMMITTED, COORDENADOR);
}

int Servidor::getDecision() {
    MPIUtil correio;
    bool recebeu = false;
    int mensagem;
    int origem;
    correio.enviar(GET_DECISION, COORDENADOR);
    time_t inicio = time(NULL);
    time_t fim = time(NULL);
    correio.receberNB(&mensagem);
    while (!recebeu && (difftime(fim, inicio) < ESPERA_MAXIMA)) {
        recebeu = correio.testaRecerber(&origem);
        sleep(1);
        fim = time(NULL);
    }

    return recebeu ? YES : NO;
}

